package com.kaikeba.homework.spring.util;

import com.kaikeba.homework.spring.ioc.BeanDefinition;
import com.kaikeba.homework.spring.ioc.PropertyValue;
import com.kaikeba.homework.spring.ioc.RuntimeBeanReference;
import com.kaikeba.homework.spring.ioc.TypedStringValue;

import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class BeanUtil {

    private static BeanUtil instance;

    private Map<String, Object> singletonObjects = new HashMap<>();

    private Map<String, BeanDefinition> beanMap = new HashMap<>();

    public static BeanUtil getInstance() {
        return instance;
    }

    public static BeanUtil build(Map<String, BeanDefinition> beanMap) {
        if (instance == null) {
            instance = new BeanUtil(beanMap);
        }
        return instance;
    }

    /**
     * 私有构造
     */
    private BeanUtil(Map<String, BeanDefinition> beanMap) {
        this.beanMap = beanMap;
    }

    /**
     * 思路： * 第一步：Bean的实例化（new对象） * 第二步：Bean的属性填充（依赖注入） *
     * 第三步：Bean的初始化（调用初始化方法--init-method标签属性，或者实现了InitializingBean接口）
     *
     * @param bd
     * @return
     */
    private Object createBean(BeanDefinition bd) {
        Class<?> clazzType = bd.getClazzType();
        if (clazzType == null) {
            return null;
        }
        // 第一步：Bean的实例化（new对象）
        Object bean = createBeanInstance(clazzType);

        // 第二步：Bean的属性填充（依赖注入）
        populateBean(bean, bd);

        // 第三步：Bean的初始化
        initializeBean(bean, bd);

        return bean;
    }

    /**
     * 一般该方法中会处理init-method标签和InitializingBean接口的初始化工作 还会处理Aware接口
     *
     * @param bean
     * @param bd
     */
    private void initializeBean(Object bean, BeanDefinition bd) {
        // TODO 处理Aware接口（标记）

        // TODO 处理InitializingBean的初始化操作

        // 处理初始化方法
        invokeInitMethod(bean, bd);
    }

    private void invokeInitMethod(Object bean, BeanDefinition bd) {
        try {
            String initMethod = bd.getInitMethod();
            if (initMethod == null || "".equals(initMethod)) {
                return;
            }
            Class<?> clazzType = bd.getClazzType();
            Method method = clazzType.getMethod(initMethod);

            method.invoke(bean);
        } catch (Exception e) {
            e.printStackTrace();
        }

    }


    /**
     * 默认使用的是无参构造
     *
     * @param clazzType
     * @return
     */
    private Object createBeanInstance(Class<?> clazzType) {
        try {
            Constructor<?> constructor = clazzType.getConstructor();
            return constructor.newInstance();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    private void populateBean(Object bean, BeanDefinition bd) {
        List<PropertyValue> propertyValues = bd.getPropertyValues();
        for (PropertyValue pv : propertyValues) {
            String name = pv.getName();
            Object value = pv.getValue();
            // 处理参数
            Object valueToUse = resolveValue(value);

            setProperty(bean, name, valueToUse, bd);
        }

    }

    private void setProperty(Object bean, String name, Object valueToUse, BeanDefinition bd) {
        try {
            Class<?> clazzType = bd.getClazzType();
            Field field = clazzType.getDeclaredField(name);
            field.setAccessible(true);
            field.set(bean, valueToUse);
        } catch (Exception e) {
            e.printStackTrace();
        }

    }

    private Object resolveValue(Object value) {
        if (value instanceof TypedStringValue) {
            TypedStringValue tsv = (TypedStringValue) value;
            String stringValue = tsv.getValue();
            Class<?> targetType = tsv.getTargetType();

            // TODO 可以优化
            if (targetType == Integer.class) {
                return Integer.parseInt(stringValue);
            } else if (targetType == String.class) {
                return stringValue;
            }
        } else if (value instanceof RuntimeBeanReference) {
            RuntimeBeanReference rbr = (RuntimeBeanReference) value;
            String ref = rbr.getRef();
            return getBean(ref);
        }
        return null;
    }


    /**
     * 整体思路：以上的方法，由于写死的代码，只能创建UserServiceImpl实现类。 *
     * 如果想要获取任意对象的话，创建Bean的代码，就不能写死。----
     * 进行XML配置（bean的类型信息、bean属性信息、bean和bean的关系）---参考spring的配置文件 * 加载并解析对应的XML信息 --
     * BeanDefinition对象（只是相当于一个bean的信息，也就是说一个bean对应一个BeanDefinition） *
     * 根据beanName去获取对应的解析出来的信息（BeanDefinition），去创建Bean实例。 * 注意事项： *
     * 什么时机去解析对应的XML？--- 一般来说，XML只需要被解析一次即可。所以说，在创建Bean之前，需要一次性将XML解析完毕 *
     * 是否需要重复创建一个Bean？ ---
     * 一般来说，如果一个Bean是一个非状态Bean（Spring中管理的都是非状态bean），都是建议使用单例方式创建。 *
     * 什么时候创建Bean实例呢？第一次调用getBean方法的时候
     *
     * @param beanName
     * @return
     */
    public Object getBean(String beanName) {
        // 先根据beanName去singletonObjects查询
        Object bean = singletonObjects.get(beanName);
        // 如果有结果，则直接返回该实例
        if (bean != null) {
            return bean;
        }
        // 如果没有结果，则需要创建对应的Bean实例
        // 先根据beanName去beanDefinitions获取对应的BeanDefinition（封装了该Bean创建需要的相关信息）
        BeanDefinition bd = beanMap.get(beanName);
        if (bd == null) {
            return null;
        }
        // 根据BeanDefinition中封装的信息来判断要创建的Bean是单例方式创建，还是多例方式创建
        // 如果是单例方式创建实例，则需要将创建后的Bean存储到singletonObjects
        if (bd.isSingleton()) {
            bean = createBean(bd);
            singletonObjects.put(beanName, bean);
        } else if (bd.isPrototype()) {
            // 如果是多例方式创建实例，则不需要将创建后的Bean存储到singletonObjects
            bean = createBean(bd);
        }

        return bean;
    }
}
